package com.platform.uaa.controller;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.http.Header;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.platform.aspect.WebLog;
import com.platform.comm.config.properties.JwtProperties;
import com.platform.comm.constants.GlobalConstant;
import com.platform.comm.jwt.JwtTokenUtil;
import com.platform.comm.util.RedisUtil;
import com.platform.comm.web.results.DataJsonResult;
import com.platform.comm.web.results.JsonResult;
import com.platform.dto.NcloudSysOnlineUserDto;
import com.platform.uaa.dto.Oauth2TokenDto;
import com.platform.utils.ServletUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.security.KeyPair;
import java.security.Principal;
import java.security.interfaces.RSAPublicKey;
import java.time.LocalDateTime;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 自定义Oauth2获取令牌接口
 *
 * @author zyk
 */
@RestController
@RequestMapping
@Slf4j
public class AuthController {

    private final JwtProperties jwtProperties;
    private final TokenEndpoint tokenEndpoint;
    private final KeyPair keyPair;
    private final StringRedisTemplate redisTemplate;
    private final JwtTokenUtil jwtTokenUtil;
    private final RedisUtil redisUtil;
    public final static String LOGIN_TOKEN_KEY = GlobalConstant.BASE_REDIS_CACHE_PREFIX + "login_tokens:";

    public AuthController(JwtProperties jwtProperties, TokenEndpoint tokenEndpoint, KeyPair keyPair, StringRedisTemplate redisTemplate,
                          JwtTokenUtil jwtTokenUtil, RedisUtil redisUtil) {
        this.jwtProperties = jwtProperties;
        this.tokenEndpoint = tokenEndpoint;
        this.keyPair = keyPair;
        this.redisTemplate = redisTemplate;
        this.jwtTokenUtil = jwtTokenUtil;
        this.redisUtil = redisUtil;
    }

    /**
     * 获取token&刷新token接口
     * TODO: 处理刷新过期&登录成功事件
     */
    @PostMapping("/oauth/token")
    @WebLog(theme = "用户登录", moduleName = "登录管理", type = WebLog.OpType.LOG_IN, root = Integer.class)
    public DataJsonResult<Oauth2TokenDto> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters)
            throws HttpRequestMethodNotSupportedException {
        OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
        Oauth2TokenDto oauth2TokenDto = Oauth2TokenDto.builder()
                .accessToken(Objects.requireNonNull(oAuth2AccessToken).getValue())
                .refreshToken(oAuth2AccessToken.getRefreshToken().getValue())
                .expiresIn(oAuth2AccessToken.getExpiresIn())
                .tokenHead(jwtProperties.getTokenHead())
                .roles(CollectionUtil.toList(((JwtTokenUtil.JwtUser) oAuth2AccessToken
                        .getAdditionalInformation().get(JwtTokenUtil.CLAIM_KEY_USER))
                        .getAuth().split(",")))
                .build();
        // 在线用户信息
        generateOnlineUserInfo(oAuth2AccessToken);
        return DataJsonResult.succeed(oauth2TokenDto);
    }

    /**
     * 登出注销
     *
     * @param request
     * @return
     */
    @PostMapping("/oauth/logout")
    public JsonResult logout(HttpServletRequest request) {
        Claims claims = jwtTokenUtil.getClaimsFromToken(request.getHeader(jwtProperties.getHeader()).replace(jwtProperties.getTokenHead(), Strings.EMPTY));
        // jwt的唯一标识
        String jti = MapUtil.getStr(claims, jwtProperties.getJwtJti());
        // jwt过期时间
        Long expireTime = MapUtil.getLong(claims, jwtProperties.getJwtExp());
        if (expireTime != null) {
            long currentTime = System.currentTimeMillis() / 1000;
            if (expireTime > currentTime) {
                redisTemplate.opsForValue().set("auth:token:blacklist:" + jti, "", (expireTime - currentTime), TimeUnit.SECONDS);
            }
        } else {
            redisTemplate.opsForValue().set("auth:token:blacklist:" + jti, null);
        }
        // 删除在线用户redis缓存
        redisUtil.del(AuthController.LOGIN_TOKEN_KEY + jti);
        return JsonResult.success();
    }


    /**
     * 远程获取rsa公钥
     *
     * @return
     */
    @GetMapping("/rsa/publicKey")
    public Map<String, Object> getKey() {
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAKey key = new RSAKey.Builder(publicKey).build();
        return new JWKSet(key).toJSONObject();
    }

    private String getTokenKey(String token) {
        return LOGIN_TOKEN_KEY + token;
    }


    /**
     * 封装在线用户信息
     *
     * @param oAuth2AccessToken token
     */
    private void generateOnlineUserInfo(OAuth2AccessToken oAuth2AccessToken) {
        try {
            HttpServletRequest request = ServletUtils.getRequest();
            assert request != null;
            UserAgent ua = UserAgentUtil.parse(request.getHeader(Header.USER_AGENT.toString()));
            String browser = ua.getBrowser().getName();
            String platForm = ua.getPlatform().getName();
            Map<String, Object> requestHeaders = getRequestHeaders(request);
            String host = requestHeaders.get("host").toString();
            if (!com.google.common.base.Strings.isNullOrEmpty(host)) {
                host = host.substring(0, host.lastIndexOf(":"));
            }
            JwtTokenUtil.JwtUser user = (JwtTokenUtil.JwtUser) oAuth2AccessToken.getAdditionalInformation().get("user");
            String jti = oAuth2AccessToken.getAdditionalInformation().get("jti").toString();
            NcloudSysOnlineUserDto onlineUserDto = NcloudSysOnlineUserDto.builder()
                    .tokenId(jti)
                    .browser(browser)
                    .os(platForm)
                    .userName(user.getRealName())
                    .departName(user.getDepartName())
                    .loginTime(LocalDateTime.now())
                    .ipaddr(host)
                    .status("在线")
                    .build();
            redisUtil.set(getTokenKey(jti), onlineUserDto, jwtProperties.getExpiration());
        } catch (Exception e) {
            log.error("在线用户信息获取失败");
        }
    }

    /**
     * 获取request的header
     *
     * @param request
     * @return
     */
    public static Map<String, Object> getRequestHeaders(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>(8);
        // 获取请求头
        Enumeration<String> enumeration = request.getHeaderNames();
        while (enumeration.hasMoreElements()) {
            //将key全部转小写
            String name = enumeration.nextElement().toLowerCase();
            String value = request.getHeader(name);
            map.put(name, value);
        }
        return map;
    }


}
